#define _DEBUG_SPRITEFONTLABEL

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using WPCoco.Graphics;

namespace WPCoco
{
    /// <summary>
    /// Label node that use the XNA SpriteFont fonts structures
    /// </summary>
    public class SpriteFontLabel : AffineNode, IColored, IGraphicState
    {
        /// <summary>
        /// Create onf label by specifiying the SpriteFont to use
        /// </summary>
        /// <param name="font">SpriteFont to use</param>
        public SpriteFontLabel(SpriteFont font)
        {
            Font = font;
            Width = 0; Height = 0;
            Anchor = Vector2.Zero;
            Color = Color.White;
        }

        CachedSpriteBatch batch = null;

        public override void SelfDraw(GameTime gameTime)
        {
            if (color.A == 0) return;

            batch = SpriteBatchCache.GetVolatile();
            batch.Begin(SpriteSortMode.Deferred, blend, sampler, depthStencil, rasterizer, null, WorldMatrix.Shared.CurrentMatrix);
            Vector2 pos = Vector2.Zero;
            Vector2 shadowOffset = new Vector2(2, 2) * fontScaleFactor;
            switch (horizontalAlignment)
            {
                case HorizontalAlignment.Top:
                    pos = Vector2.Zero;
                    break;
                case HorizontalAlignment.Center:
                    pos = new Vector2(0, Height / 2 - contentSize.Y / 2);
                    break;
                case HorizontalAlignment.Bottom:
                    pos = new Vector2(0, Height - contentSize.Y);
                    break;
            }
            for (int i = 0; i < count; i++)
            {
                if (verticalAlignment == VerticalAlignment.Right)
                    pos.X = Width - widths[i];
                else
                    if (verticalAlignment == VerticalAlignment.Center)
                        pos.X = Width / 2 - widths[i] / 2;
                if (dropShadow && shadowColor.A != 0)
                    batch.DrawString(font, lines[i], pos + shadowOffset, shadowColor, 0, Vector2.Zero, fontScaleFactor, SpriteEffects.None, 0);
                batch.DrawString(font, lines[i], pos, color, 0, Vector2.Zero, fontScaleFactor, SpriteEffects.None, 0);
                pos.Y += font.LineSpacing * lineSpacingFactor * fontScaleFactor;
            }
            batch.End();
#if DEBUG_SPRITEFONTLABEL
            GraphicPrimitives.WorldMatrix = WorldMatrix.Shared.CurrentMatrix;
            GraphicPrimitives.DrawRectangle(Vector2.Zero, new Vector2(Width, Height), Color.Red);
#endif
        }

        SpriteFont font;
        /// <summary>
        /// SpriteFont to use in label
        /// </summary>
        public SpriteFont Font
        {
            get { return font; }
            set 
            { 
                font = value;
                UpdateText();
            }
        }

        List<StringBuilder> lines = new List<StringBuilder>();
        List<float> widths = new List<float>();
        
        int count;

        void ClearLines() 
        {
            count = 0;
        }

        void AddLine(StringBuilder sb) 
        {
            if (count >= lines.Count)
            {
                lines.Add(new StringBuilder());
                widths.Add(0);
            }
            lines[count].Remove(0, lines[count].Length);
            lines[count].Append(sb);
            widths[count] = (font.MeasureString(lines[count]) * fontScaleFactor).X;
            count++;
        }

        void AddLine(string str)
        {
            while (count >= lines.Count)
            {
                lines.Add(new StringBuilder());
                widths.Add(0);
            }
            lines[count].Remove(0, lines[count].Length);
            lines[count].Append(str);
            widths[count] = (font.MeasureString(str) * fontScaleFactor).X;
            count++;
        }
        

        string text = "";
        /// <summary>
        /// Text that contain the label
        /// </summary>
        public string Text
        {
            get { return text; }
            set 
            {
                if (value == null) value = "";
                text = value;
                UpdateText();
            }
        }

        VerticalAlignment verticalAlignment = VerticalAlignment.Left;
        /// <summary>
        /// Vertical alignment of text
        /// </summary>
        public VerticalAlignment VerticalAlignment
        {
            get { return verticalAlignment; }
            set { verticalAlignment = value; }
        }

        HorizontalAlignment horizontalAlignment = HorizontalAlignment.Top;
        /// <summary>
        /// Horizontal alignment of text
        /// </summary>
        public HorizontalAlignment HorizontalAlignment
        {
            get { return horizontalAlignment; }
            set { horizontalAlignment = value; }
        }

        bool multiLine = false;
        /// <summary>
        /// Allow multi line layout
        /// </summary>
        public bool MultiLine
        {
            get { return multiLine; }
            set 
            { 
                multiLine = value;
                UpdateText();
            }
        }

        bool autoSize = true;
        /// <summary>
        /// Resize the object to the size of the content
        /// </summary>
        public bool AutoSize
        {
            get { return autoSize; }
            set 
            { 
                autoSize = value;
                UpdateText();
            }
        }

        float lineSpacingFactor = 0.8f;
        /// <summary>
        /// For shrink the space betwen the lines 
        /// </summary>
        public float LineSpacingFactor
        {
            get { return lineSpacingFactor; }
            set 
            { 
                lineSpacingFactor = value;
                UpdateText();
            }
        }

        float fontScaleFactor = 1f;
        /// <summary>
        /// Factor used for the font size
        /// </summary>
        public float FontScaleFactor
        {
            get { return fontScaleFactor; }
            set 
            {
                if (fontScaleFactor == value) return;
                fontScaleFactor = value;
                UpdateText();
            }
        }

        bool dropShadow = false;
        /// <summary>
        /// Drop a shadow behind the text 
        /// </summary>
        public bool DropShadow
        {
            get { return dropShadow; }
            set { dropShadow = value; }
        }

        void UpdateShadowColor() 
        {
            shadowColor = preferredShadowColor;
            shadowColor.A = (byte)(color.A * preferredShadowColor.A / 255); 
        }

        Color shadowColor = Color.Black;
        Color preferredShadowColor = Color.Black;
        /// <summary>
        /// Color for use in the shadow
        /// </summary>
        public Color ShadowColor
        {
            get { return shadowColor; }
            set 
            {
                preferredShadowColor = value;
                UpdateShadowColor();
            }
        }

        StringBuilder sb = new StringBuilder();
        Vector2 contentSize = Vector2.Zero;

        Vector2 GetContentSize() 
        {
            Vector2 res = Vector2.Zero, size = Vector2.Zero;
            for (int i = 0; i < count; i++)
            {
                size = Font.MeasureString(lines[i]) * fontScaleFactor;
                res.X = Math.Max(size.X, res.X);
                res.Y += font.LineSpacing * lineSpacingFactor * fontScaleFactor;
            }
            res.Y += font.LineSpacing * lineSpacingFactor * fontScaleFactor * 0.5f;
            return res;
        }

        void UpdateSize() 
        {
            Vector2 res = GetContentSize();
            Width = res.X;
            Height = res.Y;
        }

        void UpdateText() 
        {
            Vector2 size = Vector2.Zero;
            ClearLines();
            if (autoSize)
            {
                size = font.MeasureString(text) * fontScaleFactor;
                string[] lns = text.Split('\n');
                for (int i = 0; i < lns.Length; i++)
                    AddLine(lns[i]);    
                UpdateSize();
            }
            else
            {
                sb.Remove(0, sb.Length);
                Vector2 dsize = new Vector2(Width, Height);
                if (!multiLine)
                {
                    string[] lns = text.Split('\n');
                    Vector2 pos = Vector2.Zero;
                    for (int j = 0; j < lns.Length; j++)
                    {
                        string chunk = lns[j]; sb.Remove(0, sb.Length);
                        for (int i = 0; i < chunk.Length; i++)
                        {
                            sb.Append(chunk[i]);
                            size = font.MeasureString(sb) * fontScaleFactor;
                            if (size.X > dsize.X)
                            {
                                sb.Remove(sb.Length - 1, 1);
                                break;
                            }
                        }
                        if (sb.Length > 0)
                        {
                            pos.Y += font.LineSpacing * lineSpacingFactor * fontScaleFactor;
                            if (pos.Y > dsize.Y) break;
                            AddLine(sb);
                        }
                    }
                }
                else
                {
                    int i = 0, k = 0, last; bool br = false;
                    if (dsize.Y >= font.LineSpacing * lineSpacingFactor * fontScaleFactor)
                    {
                        while (i < text.Length)
                        {
                            k = i + 1; br = false;
                            if (k == text.Length) break;
                            while (k < text.Length)
                            {
                                if (char.IsWhiteSpace(text[k]))
                                {
                                    br = text[k] == '\n';
                                    break;
                                }
                                k++;
                            }
                            if (k == text.Length) k--;
                            last = sb.Length;
                            sb.Append(text, i, k - i + 1);
                            size = font.MeasureString(sb) * fontScaleFactor;
                            if (size.X > dsize.X || br)
                            {
                                if (br)
                                {
                                    AddLine(sb.ToString(0, sb.Length - 1)); k++;
                                    sb.Remove(0, sb.Length);
                                }
                                else
                                {
                                    AddLine(sb.ToString(0, last));
                                    sb.Remove(0, last + 1);
                                }
                                dsize.Y -= font.LineSpacing * lineSpacingFactor * fontScaleFactor;
                                if (dsize.Y < font.LineSpacing * lineSpacingFactor * fontScaleFactor) break;
                            }
                            i = k;
                        }
                        if (sb.Length != 0 && dsize.Y >= font.LineSpacing * lineSpacingFactor * fontScaleFactor)
                        {
                            size = font.MeasureString(sb) * fontScaleFactor;
                            if (size.X <= dsize.X)
                            {
                                AddLine(sb);
                            }
                        }
                    }
                }
            }
            contentSize = GetContentSize();
        }

        #region IColored Members

        Color color = Color.White;
        /// <summary>
        /// Color of the text
        /// </summary>
        public Color Color
        {
            get { return color; }
            set 
            { 
                color = value;
                UpdateShadowColor();
            }
        }

        #endregion

        #region IGraphicState Members

        SamplerState sampler;

        public SamplerState Sampler
        {
            get { return sampler; }
            set { sampler = value; }
        }

        DepthStencilState depthStencil;

        public DepthStencilState DepthStencil
        {
            get { return depthStencil; }
            set { depthStencil = value; }
        }

        BlendState blend = BlendState.AlphaBlend;

        public BlendState Blend
        {
            get { return blend; }
            set { blend = value; }
        }

        RasterizerState rasterizer;

        public RasterizerState Rasterizer
        {
            get { return rasterizer; }
            set { rasterizer = value; }
        }

        #endregion
    }
}
